// Copyright Paul A. Bristow 2013
// Copyright John Maddock 2013
// Copyright Christopher Kormanyos

// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt
// or copy at http://www.boost.org/LICENSE_1_0.txt)

// Examples of std::numeric_limits usage as snippets for multiprecision documentation at multiprecision.qbk.

// Includes text as Quickbook comments.

#include <boost/assert.hpp>

#include <boost/math/constants/constants.hpp>
#include <boost/math/special_functions/nonfinite_num_facets.hpp>

#include <boost/math/special_functions/factorials.hpp>
#include <boost/math/special_functions/next.hpp>
#include <boost/math/tools/precision.hpp>

#include <nil/crypto3/multiprecision/cpp_dec_float.hpp>    // is decimal.
#include <nil/crypto3/multiprecision/cpp_bin_float.hpp>    // is binary.

#define BOOST_TEST_MAIN
#include <boost/test/unit_test.hpp>    // Boost.Test
#include <boost/test/tools/floating_point_comparison.hpp>

#include <iostream>
#include <iomanip>
#include <string>
#include <sstream>
#include <limits>    // numeric_limits
#include <iomanip>
#include <locale>

// static long double const log10Two = 0.30102999566398119521373889472449L; // log10(2.)
// It is more portable useful to use a Boost macro
// See https://www.boost.org/doc/libs/release/libs/config/doc/html/boost_config/boost_macro_reference.html
BOOST_STATIC_CONSTEXPR long double log10Two = 0.30102999566398119521373889472449L;
// which expands to static constexpr on standard C++11 and up, but static const on earlier versions.

/*`By default, output would only show the standard 6 decimal digits,
so set precision to show all 50 significant digits, including any trailing zeros.
This is generally useful to show the implicit precision of the type of the value.
*/

template<typename T>
int max_digits10() {
    int significand_digits = std::numeric_limits<T>::digits;
    // BOOST_CONSTEXPR_OR_CONST int significand_digits = std::numeric_limits<T>::digits;
    return static_cast<int>(ceil(1 + significand_digits * log10Two));
}    // template <typename T> int max_digits10()

// Used to test max_digits10<>() function below.
//#define BOOST_NO_CXX11_NUMERIC_LIMITS

BOOST_AUTO_TEST_CASE(test_numeric_limits_snips) {
#if !(defined(CI_SUPPRESS_KNOWN_ISSUES) && defined(BOOST_MSVC) && (BOOST_MSVC == 1600))
    try {

        // Example of portable way to get `std::numeric_limits<T>::max_digits10`.
        //[max_digits10_1

        /*`For example, to be portable (including obselete platforms) for type `T` where `T` may be:
         `float`, `double`, `long double`, `128-bit quad type`, `cpp_bin_float_50` ...
        */

        typedef float T;

#if defined BOOST_NO_CXX11_NUMERIC_LIMITS
        // No max_digits10 implemented.
        std::cout.precision(max_digits10<T>());
#else
#if (_MSC_VER <= 1600)
        //  The MSVC 2010 version had the wrong value for std::numeric_limits<float>::max_digits10.
        std::cout.precision(max_digits10<T>());
#else    // Use the C++11 max_digits10.
        std::cout.precision(std::numeric_limits<T>::max_digits10);
        std::cout.precision(std::numeric_limits<T>::digits10);
        std::cout.setf(std::ios_base::showpoint);    // Append any trailing zeros,
        // or more memorably
        std::cout << std::showpoint << std::endl;    //
#endif
#endif

        std::cout << "std::cout.precision(max_digits10) = " << std::cout.precision() << std::endl;    // 9

        double x = 1.2345678901234567889;

        std::cout << "x = " << x << std::endl;    //

        /*`which should output:

          std::cout.precision(max_digits10) = 9
          x = 1.23456789
        */

        //] [/max_digits10_1]

        {
            //[max_digits10_2

            double write = 2. / 3;    // Any arbitrary value that cannot be represented exactly.
            double read = 0;
            std::stringstream s;
            s.precision(std::numeric_limits<double>::digits10);    // or `float64_t` for 64-bit IEE754 double.
            s << write;
            s >> read;
            if (read != write) {
                std::cout << std::setprecision(std::numeric_limits<double>::digits10) << read << " != " << write
                          << std::endl;
            }

            //] [/max_digits10_2]
            // 0.666666666666667 != 0.666666666666667
        }

        {
            //[max_digits10_3

            double pi = boost::math::double_constants::pi;
            std::cout.precision(std::numeric_limits<double>::max_digits10);
            std::cout << pi << std::endl;    // 3.1415926535897931

            //] [/max_digits10_3]
        }
        {
            //[max_digits10_4
            /*`and similarly for a much higher precision type:
             */

            using namespace nil::crypto3::multiprecision;

            typedef number<cpp_dec_float<50>> cpp_dec_float_50;    // 50 decimal digits.

            // or using nil::crypto3::multiprecision::cpp_dec_float_50;

            cpp_dec_float_50 pi = boost::math::constants::pi<cpp_dec_float_50>();
            std::cout.precision(std::numeric_limits<cpp_dec_float_50>::max_digits10);
            std::cout << pi << std::endl;
            // 3.141592653589793238462643383279502884197169399375105820974944592307816406
            //] [/max_digits10_4]
        }

        {
            //[max_digits10_5

            for (int i = 2; i < 15; i++) {
                std::cout << std::setw(std::numeric_limits<int>::max_digits10) << boost::math::factorial<double>(i)
                          << std::endl;
            }

            //] [/max_digits10_5]
        }

    } catch (const std::exception& ex) {
        std::cout << "Caught Exception " << ex.what() << std::endl;
    }

    {
        //[max_digits10_6

        typedef double T;

        bool denorm = std::numeric_limits<T>::denorm_min() < (std::numeric_limits<T>::min)();
        BOOST_ASSERT(denorm);

        //] [/max_digits10_6]
    }

    {
        unsigned char c = 255;
        std::cout << "char c = " << (int)c << std::endl;
    }

    {
        //[digits10_1
        std::cout << std::setw(std::numeric_limits<short>::digits10 + 1 + 1)    // digits10+1, and +1 for sign.
                  << std::showpos << (std::numeric_limits<short>::max)()        // +32767
                  << std::endl
                  << std::setw(std::numeric_limits<short>::digits10 + 1 + 1) << (std::numeric_limits<short>::min)()
                  << std::endl;    // -32767
                                   //] [/digits10_1]
    }

    {
        //[digits10_2
        std::cout << std::setw(std::numeric_limits<unsigned short>::digits10 + 1 + 1)    // digits10+1, and +1 for sign.
                  << std::showpos << (std::numeric_limits<unsigned short>::max)()        //  65535
                  << std::endl
                  << std::setw(std::numeric_limits<unsigned short>::digits10 + 1 + 1)    // digits10+1, and +1 for sign.
                  << (std::numeric_limits<unsigned short>::min)() << std::endl;          //      0
                                                                                         //] [/digits10_2]
    }

    std::cout << std::noshowpos << std::endl;

    {
        //[digits10_3
        std::cout.precision(std::numeric_limits<double>::max_digits10);
        double d = 1e15;
        double dp1 = d + 1;
        std::cout << d << "\n" << dp1 << std::endl;
        // 1000000000000000
        // 1000000000000001
        std::cout << dp1 - d << std::endl;    // 1
                                              //] [/digits10_3]
    }

    {
        //[digits10_4
        std::cout.precision(std::numeric_limits<double>::max_digits10);
        double d = 1e16;
        double dp1 = d + 1;
        std::cout << d << "\n" << dp1 << std::endl;
        // 10000000000000000
        // 10000000000000000
        std::cout << dp1 - d << std::endl;    // 0 !!!
                                              //] [/digits10_4]
    }

    {
        //[epsilon_1
        std::cout.precision(std::numeric_limits<double>::max_digits10);
        double d = 1.;
        double eps = std::numeric_limits<double>::epsilon();
        double dpeps = d + eps;
        std::cout << std::showpoint         // Ensure all trailing zeros are shown.
                  << d << "\n"              // 1.0000000000000000
                  << dpeps << std::endl;    // 2.2204460492503131e-016
        std::cout << dpeps - d              // 1.0000000000000002
                  << std::endl;
        //] [epsilon_1]
    }

    {
        //[epsilon_2
        double one = 1.;
        double nad = boost::math::float_next(one);
        std::cout << nad << "\n"    //  1.0000000000000002
                  << nad - one      // 2.2204460492503131e-016
                  << std::endl;
        //] [epsilon_2]
    }
    {
        //[epsilon_3
        std::cout.precision(std::numeric_limits<double>::max_digits10);
        double d = 1.;
        double eps = std::numeric_limits<double>::epsilon();
        double dpeps = d + eps / 2;

        std::cout << std::showpoint           // Ensure all trailing zeros are shown.
                  << dpeps << "\n"            // 1.0000000000000000
                  << eps / 2 << std::endl;    // 1.1102230246251565e-016
        std::cout << dpeps - d                // 0.00000000000000000
                  << std::endl;
        //] [epsilon_3]
    }

    {
        typedef double RealType;
        //[epsilon_4
        /*`A tolerance might be defined using this version of epsilon thus:
         */
        RealType tolerance = boost::math::tools::epsilon<RealType>() * 2;
        //] [epsilon_4]
        (void)tolerance;    // warning suppression
    }

    {
        bool b =
            //[digits10_5
            -(std::numeric_limits<double>::max)() == std::numeric_limits<double>::lowest();
        //] [/digits10_5]
        (void)b;    // warning suppression
    }

    {
        //[denorm_min_1
        std::cout.precision(std::numeric_limits<double>::max_digits10);
        if (std::numeric_limits<double>::has_denorm == std::denorm_present) {
            double d = std::numeric_limits<double>::denorm_min();

            std::cout << d << std::endl;    //  4.9406564584124654e-324

            int exponent;

            double significand = frexp(d, &exponent);
            std::cout << "exponent = " << std::hex << exponent << std::endl;          //  fffffbcf
            std::cout << "significand = " << std::hex << significand << std::endl;    // 0.50000000000000000
        } else {
            std::cout << "No denormalization. " << std::endl;
        }
        //] [denorm_min_1]
    }

    {
        //[round_error_1
        double round_err = std::numeric_limits<double>::epsilon()           // 2.2204460492503131e-016
                           * std::numeric_limits<double>::round_error();    // 1/2
        std::cout << round_err << std::endl;                                // 1.1102230246251565e-016
                                                                            //] [/round_error_1]
    }

    {
        typedef double T;
        //[tolerance_1
        /*`For example, if we want a tolerance that might suit about 9 arithmetical operations,
        say sqrt(9) = 3,  we could define:
        */

        T tolerance = 3 * std::numeric_limits<T>::epsilon();

        /*`This is very widely used in Boost.Math testing
        with Boost.Test's macro `BOOST_CHECK_CLOSE_FRACTION`
        */

        T expected = 1.0;
        T calculated = 1.0 + std::numeric_limits<T>::epsilon();

        BOOST_CHECK_CLOSE_FRACTION(expected, calculated, tolerance);

        //] [/tolerance_1]
    }

#if !(defined(CI_SUPPRESS_KNOWN_ISSUES) && defined(__GNUC__) && defined(_WIN32))
    {
        //[tolerance_2

        using nil::crypto3::multiprecision::cpp_dec_float;
        using nil::crypto3::multiprecision::et_off;
        using nil::crypto3::multiprecision::number;

        typedef number<cpp_dec_float<50>, et_off> cpp_dec_float_50;    // 50 decimal digits.
        /*`[note that Boost.Test does not yet allow floating-point comparisons with expression templates on,
        so the default expression template parameter has been replaced by `et_off`.]
        */

        cpp_dec_float_50 tolerance = 3 * std::numeric_limits<cpp_dec_float_50>::epsilon();
        cpp_dec_float_50 expected = boost::math::constants::two_pi<cpp_dec_float_50>();
        cpp_dec_float_50 calculated = 2 * boost::math::constants::pi<cpp_dec_float_50>();

        BOOST_CHECK_CLOSE_FRACTION(expected, calculated, tolerance);

        //] [/tolerance_2]
    }

    {
        //[tolerance_3

        using nil::crypto3::multiprecision::cpp_bin_float_quad;

        cpp_bin_float_quad tolerance = 3 * std::numeric_limits<cpp_bin_float_quad>::epsilon();
        cpp_bin_float_quad expected = boost::math::constants::two_pi<cpp_bin_float_quad>();
        cpp_bin_float_quad calculated = 2 * boost::math::constants::pi<cpp_bin_float_quad>();

        BOOST_CHECK_CLOSE_FRACTION(expected, calculated, tolerance);

        //] [/tolerance_3]
    }

    {
        //[tolerance_4

        using nil::crypto3::multiprecision::cpp_bin_float_oct;

        cpp_bin_float_oct tolerance = 3 * std::numeric_limits<cpp_bin_float_oct>::epsilon();
        cpp_bin_float_oct expected = boost::math::constants::two_pi<cpp_bin_float_oct>();
        cpp_bin_float_oct calculated = 2 * boost::math::constants::pi<cpp_bin_float_oct>();

        BOOST_CHECK_CLOSE_FRACTION(expected, calculated, tolerance);

        //] [/tolerance_4]
    }

    {
        //[nan_1]

        /*`NaN can be used with binary multiprecision types like `cpp_bin_float_quad`:
         */
        using nil::crypto3::multiprecision::cpp_bin_float_quad;

        if (std::numeric_limits<cpp_bin_float_quad>::has_quiet_NaN == true) {
            cpp_bin_float_quad NaN = std::numeric_limits<cpp_bin_float_quad>::quiet_NaN();
            std::cout << "cpp_bin_float_quad NaN is " << NaN << std::endl;    //   cpp_bin_float_quad NaN is nan

            cpp_bin_float_quad expected = NaN;
            cpp_bin_float_quad calculated = 2 * NaN;
            // Comparisons of NaN's always fail:
            bool b = expected == calculated;
            std::cout << b << std::endl;
            BOOST_CHECK_NE(expected, expected);
            BOOST_CHECK_NE(expected, calculated);
        } else {
            std::cout << "Type " << typeid(cpp_bin_float_quad).name() << " does not have NaNs!" << std::endl;
        }

        //]  [/nan_1]
    }

    {
        //[facet_1]

        /*`
        See [@boost:/libs/math/example/nonfinite_facet_sstream.cpp]
        and we also need

          #include <boost/math/special_functions/nonfinite_num_facets.hpp>

        Then we can equally well use a multiprecision type cpp_bin_float_quad:

        */
        using nil::crypto3::multiprecision::cpp_bin_float_quad;

        typedef cpp_bin_float_quad T;

        using boost::math::nonfinite_num_get;
        using boost::math::nonfinite_num_put;
        {
            std::locale old_locale;
            std::locale tmp_locale(old_locale, new nonfinite_num_put<char>);
            std::locale new_locale(tmp_locale, new nonfinite_num_get<char>);
            std::stringstream ss;
            ss.imbue(new_locale);
            T inf = std::numeric_limits<T>::infinity();
            ss << inf;    // Write out.
            BOOST_ASSERT(ss.str() == "inf");
            T r;
            ss >> r;                   // Read back in.
            BOOST_ASSERT(inf == r);    // Confirms that the floating-point values really are identical.
            std::cout << "infinity output was " << ss.str() << std::endl;
            std::cout << "infinity input was " << r << std::endl;
        }

        /*`
        ``
          infinity output was inf
          infinity input was inf
        ``
        Similarly we can do the same with NaN (except that we cannot use `assert` (because any comparisons with NaN
        always return false).
        */
        {
            std::locale old_locale;
            std::locale tmp_locale(old_locale, new nonfinite_num_put<char>);
            std::locale new_locale(tmp_locale, new nonfinite_num_get<char>);
            std::stringstream ss;
            ss.imbue(new_locale);
            T n;
            T NaN = std::numeric_limits<T>::quiet_NaN();
            ss << NaN;    // Write out.
            BOOST_ASSERT(ss.str() == "nan");
            std::cout << "NaN output was " << ss.str() << std::endl;
            ss >> n;    // Read back in.
            std::cout << "NaN input was " << n << std::endl;
        }
        /*`
        ``
          NaN output was nan
          NaN input was nan
        ``
        */
        //]  [/facet_1]
    }

#endif
#endif
}    // BOOST_AUTO_TEST_CASE(test_numeric_limits_snips)

